Skip to content

fix(orchestrator): Skip blank subagent opening messages - #5456

Open
mwolson wants to merge 2 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/claude-subagent-empty-prompt-message
Open

fix(orchestrator): Skip blank subagent opening messages#5456
mwolson wants to merge 2 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/claude-subagent-empty-prompt-message

Conversation

@mwolson

@mwolson mwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Carried from another PR

  • fix(orchestration): Preserve Claude subagent attribution after settle
    carried for #5388 claude-postsettle-attribution.
    Review it there, not here.
  • fix(orchestrator): Skip blank subagent opening messages — this PR.

Summary

  • Stop provider-native subagent child threads from opening with an empty or
    whitespace-only user message under the "Sent by another agent" label.
  • Gate every subagent opening message with a shared trim-aware check, and emit
    Claude's opening message the first time the task prompt has real text rather
    than on first registration.
  • One unique commit above fix(orchestration): Preserve Claude subagent attribution after settle #5388 claude-postsettle-attribution on
    t3code/codex-turn-mapping.

Problem and Fix

Problem and Why it Happened Fix
A Claude Agent launch whose prompt is whitespace-only still produced a child user message. The Agent tool accepts it, task_started carries it, and the emission site had no emptiness guard, so the web timeline rendered an empty bubble. Shared hasSubagentPromptText trims before deciding. Every makeSubagentConversationArtifacts caller with role: "user" goes through it (Claude, Codex, Cursor, ACP).
A task_progress frame can register a Claude subagent before any prompt-bearing frame. Registration fell back to prompt: input.prompt ?? "", and the opening message was tied to first registration, so it was emitted as "" and never rewritten when a later task_started supplied the real prompt. Claude emits the opening message when hasSubagentPromptText(task.prompt) && !hasSubagentPromptText(priorTask?.prompt), so a late prompt still opens the child exactly once and a prompt change from one non-empty value to another does not re-emit.
Codex only checked input.prompt.length > 0, which still admits whitespace. Cursor and ACP had no guard. Codex's length check is replaced by the shared predicate; Cursor and ACP pick up the same gate.

Validation

  • New replay fixture claude_subagent_empty_prompt covers both routes (whitespace Agent prompt and task_progress before task_started). Confirmed fail before the emission fix and pass after.
  • Replay suites (OrchestratorReplayFixtures.integration / .contract, ClaudeReplayFixtures.integration, CodexReplayFixtures.integration): 89 tests, 1 skipped.
  • Adapter and projection suites (Claude, Claude testkit, Codex, Cursor, Cursor testkit, ACP, ACP registry, Grok, SubagentProjection): 274 tests.
  • vp check: passed.
  • vp run typecheck: passed all 15 packages.
  • Isolated private serve live check against this branch: a whitespace Agent launch produced a completed subagent row with prompt: " " and a child thread with no user message; a sibling ordinary-prompt child held exactly one :prompt message. Zero trim-empty user-role projection messages in the isolated DB.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (not applicable: server-side projection only)
  • I included a video for animation/interaction changes (not applicable)

Note

Low Risk
Server-side projection guard only; no auth, persistence schema, or API contract changes beyond omitting empty timeline messages.

Overview
ACP adapter now wraps subagent opening user message.updated / turn_item.updated emission in hasSubagentPromptText(update.prompt), so child threads are not seeded with a blank “Sent by another agent” bubble when the provider supplies an empty or whitespace-only prompt.

Subagent registration and other subagent events are unchanged; only the optional :prompt conversation artifacts are skipped when there is no real prompt text (trim-aware check from SubagentProjection).

Reviewed by Cursor Bugbot for commit 1598863. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Skip blank subagent opening messages in Claude, ACP, Codex, and Cursor adapters

  • Adds hasSubagentPromptText to SubagentProjection.ts to return true only when a prompt contains non-whitespace text; gates opening user message and turn item emissions in all four provider adapters on this check.
  • Refactors the Claude adapter to track subagents session-wide with generation-aware state, serialize SDK message handling via a Semaphore, and deduplicate progress/result emissions; subagent-originated approval flows are now supported.
  • Adds messageText override and failIfCurrent hook to ProviderContinuationRequest, allowing adapters to customize continuation message text and clean up state on dispatch failure.
  • Filters 'Background task completed.' provider-buffered continuation messages from the visible run queue in deriveThreadQueueWorkflowState.
  • Adds an orchestrator replay fixture claude_subagent_empty_prompt to cover the blank-prompt subagent scenario end-to-end.

Macroscope summarized 1598863.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17742a8c-14fa-4d95-accf-1cf39c0ecb77

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 5, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6e Compare August 7, 2026 12:10
mwolson added 2 commits August 8, 2026 14:41
A provider-native subagent could open its child thread with an empty or
whitespace-only user message, which renders as an empty bubble under the
"Sent by another agent" label. The Claude adapter emitted the opening
prompt whenever the subagent was new, and a task_progress frame can
register a subagent before any frame carries its prompt, so the message
was emitted with the "" default and never rewritten when the real prompt
arrived on a later task_started.

A shared hasSubagentPromptText guard now gates every subagent opening
message. The Claude adapter emits it the first time the task prompt
actually has text rather than on first registration, so a late prompt
still opens the child thread exactly once. Codex's existing length check
becomes trim-aware, and Cursor and ACP pick up the same guard.
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from 0e1b4fe to 1598863 Compare August 8, 2026 18:43
@macroscopeapp

macroscopeapp Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant